"use client"; import { MutableRefObject, useCallback, useEffect, useMemo, useRef, useState, } from "react"; import { observer } from "mobx-react-lite"; import { Item, ItemParams, Menu, useContextMenu } from "react-contexify"; import { Alert, Button, Col, Offcanvas, OffcanvasBody, OffcanvasHeader, Row, } from "reactstrap"; import Swal from "sweetalert2"; import { toast } from "react-toastify"; import { sprintf } from "sprintf-js"; import { useTo } from "taehui-ts/fe-utility"; import { useQueryClient } from "@tanstack/react-query"; import { useSiteStore } from "@/Stores"; import SiteComponent from "@/site/SiteComponent"; import ConfigureWindow from "@/site/ConfigureWindow"; import SiteWindow from "@/site/SiteWindow"; import SiteYellItems from "@/site/SiteYellItems"; import AvatarItems from "@/site/AvatarItems"; import SiteInput from "@/site/SiteInput"; import SiteYellItem from "@/site/SiteYellItem"; import { getDefaultAvatarID } from "@/Utility"; import { OnSiteYellInput } from "@/site/Site"; import scss from "@/app/[language]/site/page.module.scss"; import { useTranslations } from "next-intl"; import { usePathname } from "next/navigation"; const EventPB = require("@/Event_pb"); export default observer(() => { const { avatars, siteNotify, isAvatarsOpened, isPendingSiteYellOpened, lastPendingSiteYell, targetSiteID, siteViews, siteAvatarID, siteYells, setSiteYellsViewLowest, getSiteView, onSiteIDModified, siteYellsViewMove, setPendingSiteYellOpened, setAvatarsOpened, isLoading, setSiteWindowOpened, siteYellsView, titleComponent, } = useSiteStore(); const t = useTranslations(); const to = useTo(); const { show: viewSiteNameInput } = useContextMenu({ id: "siteName", }); const { show: viewAvatarInput } = useContextMenu({ id: "avatar", }); const { show: viewSiteYellInput } = useContextMenu({ id: "siteYell", }); const pathname = usePathname(); useEffect(() => { if (pathname === "/site") { siteYellsViewMove(); } }, [pathname, siteYellsViewMove]); const [siteYellsViewHeight, setSiteYellsViewHeight] = useState(""); const siteViewsView = useRef<HTMLDivElement>(null); const inputComponent = useRef<HTMLDivElement>(null); const onViewAvatar = ({ props: { avatarID } }: ItemParams) => { if (avatarID.startsWith("*")) { toast.warning(t("notAvatarViewFault")); } else { to(`/avatar/${encodeURIComponent("#")}${getDefaultAvatarID(avatarID)}`); } }; useEffect(() => { window.Notification?.requestPermission(); }, []); const onViewsModified = useCallback(() => { setSiteYellsViewHeight( `${ document.documentElement.clientHeight - (titleComponent?.current?.clientHeight ?? 0) - (siteViewsView.current?.clientHeight ?? 0) - (inputComponent.current?.clientHeight ?? 0) }px`, ); }, [titleComponent]); useEffect(() => { onViewsModified(); }, [onViewsModified, isLoading, avatars, lastPendingSiteYell]); useEffect(() => { const onModified = () => { onViewsModified(); siteYellsViewMove(); }; window.addEventListener("resize", onModified); return () => { window.removeEventListener("resize", onModified); }; }, [onViewsModified, siteYellsViewMove]); const onSiteYellsViewMove = useCallback(() => { if (siteYellsView) { const { current } = siteYellsView; if (current) { const siteYellID = siteYells[0]?.siteYellID; if (siteYellID && siteYellID > 0 && !current.scrollTop) { SiteComponent.send({ eventID: EventPB.Event.EventID.GET_SITE_YELLS, text: JSON.stringify({ siteID: targetSiteID, siteYellID: siteYellID, }), }); } const isSiteYellsViewLowest = current.scrollTop + current.clientHeight >= current.scrollHeight; setSiteYellsViewLowest(isSiteYellsViewLowest); if (isSiteYellsViewLowest) { setPendingSiteYellOpened(false); } } } }, [ setPendingSiteYellOpened, setSiteYellsViewLowest, siteYells, siteYellsView, targetSiteID, ]); useEffect(() => { if (siteYellsView) { const { current } = siteYellsView; if (current) { current.addEventListener("scroll", onSiteYellsViewMove); return () => { current.removeEventListener("scroll", onSiteYellsViewMove); }; } } }, [onSiteYellsViewMove, isLoading, siteYellsView]); const isSiteHand = useMemo( () => getSiteView(targetSiteID)?.siteHand === siteAvatarID, [getSiteView, siteAvatarID, targetSiteID], ); const queryClient = useQueryClient(); const onSiteYellInput: OnSiteYellInput = (event, avatarID) => { viewSiteYellInput({ event, props: { avatarID } }); }; return ( <> <div ref={siteViewsView}> <Row className="g-0"> {siteViews.map(({ siteID, siteName, isNew }) => { return ( <Col className="m-1" xs="auto" key={siteID}> <Button color={ isNew ? "warning" : siteID === targetSiteID ? "primary" : "secondary" } onClick={() => { onSiteIDModified(siteID); }} onContextMenu={(event) => { event.preventDefault(); viewSiteNameInput({ event }); }} > {siteName} </Button> </Col> ); })} <Col className="m-1" xs="auto"> <Button color="info" onClick={async () => { await queryClient.invalidateQueries({ queryKey: ["sites"] }); setSiteWindowOpened(true); }} > {t("onSiteWindow")} </Button> </Col> {targetSiteID && ( <Col className="m-1" xs="auto"> <Button onClick={() => { SiteComponent.send({ eventID: EventPB.Event("EventID.QUIT_SITE"), text: targetSiteID, }); }} color="danger" > {t("quitSite")} </Button> </Col> )} </Row> </div> <Row className="justify-content-center g-0"> <Col className="m-1" xs="auto" style={{ position: "absolute" }}> <Alert isOpen={!!siteNotify} color="primary"> <span>{siteNotify}</span> </Alert> </Col> </Row> <Row className="justify-content-center g-0 route"> <Col className="m-1" xs="auto" style={{ position: "absolute" }}> <Alert isOpen={isPendingSiteYellOpened} onClick={siteYellsViewMove}> {lastPendingSiteYell && ( <SiteYellItem data={lastPendingSiteYell} onSiteYellInput={onSiteYellInput} /> )} </Alert> </Col> </Row> <div ref={siteYellsView} className={scss.siteView} style={{ height: siteYellsViewHeight, }} > <SiteYellItems onSiteYellInput={onSiteYellInput} /> </div> <Offcanvas direction="end" isOpen={isAvatarsOpened} toggle={() => setAvatarsOpened(false)} > <OffcanvasHeader toggle={() => setAvatarsOpened(false)}> {sprintf(t("avatarCountText"), avatars.length)} </OffcanvasHeader> <OffcanvasBody> <AvatarItems onAvatarInput={(event, avatarID) => { viewAvatarInput({ event, props: { avatarID } }); }} /> </OffcanvasBody> <Menu id="avatar"> <Item disabled={!isSiteHand} onClick={({ props: { avatarID } }) => { SiteComponent.send({ eventID: EventPB.Event("EventID.SET_SITE_OWNER"), text: JSON.stringify({ siteID: targetSiteID, avatarID, }), }); }} > <span>{t("setSiteHand")}</span> </Item> <Item onClick={onViewAvatar}> <span>{t("viewAvatarView")}</span> </Item> <Item onClick={({ props: { avatarID } }) => { SiteComponent.send({ eventID: EventPB.Event("EventID.NEW_SILENT_SITE"), text: avatarID, }); }} > <span>{t("silentSiteNew")}</span> </Item> <Item disabled={!isSiteHand} onClick={({ props: { avatarID } }) => { SiteComponent.send({ eventID: EventPB.Event("EventID.EXILE_AVATAR"), text: JSON.stringify({ siteID: targetSiteID, avatarID, }), }); }} > <span>{t("exileAvatar")}</span> </Item> </Menu> </Offcanvas> <Menu id="siteYell"> <Item onClick={onViewAvatar}> <span>{t("viewAvatarView")}</span> </Item> </Menu> <Menu id="siteName"> <Item disabled={!isSiteHand} onClick={async () => { const { isConfirmed, value } = await Swal.fire({ title: t("setSiteNameText"), input: "text", }); if (isConfirmed) { SiteComponent.send({ eventID: EventPB.Event("EventID.SET_SITE_NAME"), text: JSON.stringify({ siteID: targetSiteID, siteName: value, }), }); } }} > <span>{t("setSiteName")}</span> </Item> </Menu> <div ref={inputComponent}> <SiteInput /> </div> <ConfigureWindow /> <SiteWindow /> </> ); });